/*****************************************************************************
 * AndroidDevices.java
 *
 * Copyright © 2011-2014 VLC authors and VideoLAN
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 */

package org.videolan.resources

import android.annotation.TargetApi
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.Environment
import android.telephony.TelephonyManager
import android.view.InputDevice
import android.view.MotionEvent
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.resources.util.getPackageInfoCompat
import org.videolan.tools.containsName
import org.videolan.tools.getFileNameFromPath
import org.videolan.tools.startsWith
import java.io.BufferedReader
import java.io.Closeable
import java.io.File
import java.io.FileReader
import java.io.IOException
import java.util.Locale
import java.util.StringTokenizer
import kotlin.math.absoluteValue

@TargetApi(VERSION_CODES.N)
object AndroidDevices {
    const val TAG = "VLC/UiTools/AndroidDevices"
    val EXTERNAL_PUBLIC_DIRECTORY: String = Environment.getExternalStorageDirectory().path
    val isPhone: Boolean
    val hasNavBar: Boolean
    val hasTsp: Boolean
    val isAndroidTv: Boolean
    val watchDevices: Boolean
    val isTv: Boolean
    val isAmazon = Build.MANUFACTURER == "Amazon"
    val isChromeBook: Boolean
    //Is the system PiP available on this device system
    val hasPiP: Boolean
    //Is PiP or custom PiP allowed on this device
    val pipAllowed: Boolean
    private val noMediaStyleManufacturers = arrayOf("huawei", "symphony teleca")
    val showMediaStyle = !isManufacturerBannedForMediastyleNotifications
    val hasPlayServices: Boolean

    //Devices mountpoints management
    private val typeWL = listOf("vfat", "exfat", "sdcardfs", "fuse", "ntfs", "fat32", "ext3", "ext4", "esdfs")
    private val typeBL = listOf("tmpfs")
    private val mountWL = arrayOf("/mnt", "/Removable", "/storage")
    val mountBL = arrayOf(EXTERNAL_PUBLIC_DIRECTORY, "/mnt/secure", "/mnt/shell", "/mnt/asec", "/mnt/nand", "/mnt/runtime", "/mnt/obb", "/mnt/media_rw/extSdCard", "/mnt/media_rw/sdcard", "/storage/emulated", "/var/run/arc")
    private val deviceWL = arrayOf("/dev/block/vold", "/dev/fuse", "/mnt/media_rw", "passthrough", "//")
    // Some devices should be considered as TVs
    // Amazon Echo 15 (AEOHY): has touch screen but main navigation is done by remote
    val forcedTVModels = arrayOf("AEOHY")

    /**
     * hasCombBar test if device has Combined Bar : only for tablet with Honeycomb or ICS
     */

    // skip if already in list or if type/mountpoint is blacklisted
    // check that device is in whitelist, and either type or mountpoint is in a whitelist
    val externalStorageDirectories: List<String>
        get() {
            var bufReader: BufferedReader? = null
            val list = ArrayList<String>()
            try {
                bufReader = BufferedReader(FileReader("/proc/mounts"))
                var line = bufReader.readLine()
                while (line != null) {

                    val tokens = StringTokenizer(line, " ")
                    val device = tokens.nextToken()
                    val mountpoint = tokens.nextToken().replace("\\\\040".toRegex(), " ")
                    val type = if (tokens.hasMoreTokens()) tokens.nextToken() else null
                    if (list.contains(mountpoint) || typeBL.contains(type) || startsWith(mountBL, mountpoint)) {
                        line = bufReader.readLine()
                        continue
                    }
                    if (startsWith(deviceWL, device) && (typeWL.contains(type) || startsWith(mountWL, mountpoint))) {
                        val position = containsName(list, mountpoint.getFileNameFromPath())
                        if (position > -1) list.removeAt(position)
                        list.add(mountpoint)
                    }
                    line = bufReader.readLine()
                }
            } catch (ignored: IOException) {
            } finally {
                close(bufReader)
            }
            list.remove(EXTERNAL_PUBLIC_DIRECTORY)
            return list
        }

    private val isManufacturerBannedForMediastyleNotifications: Boolean
        get() {
            if (!AndroidUtil.isMarshMallowOrLater)
                for (manufacturer in noMediaStyleManufacturers)
                    if (Build.MANUFACTURER.lowercase(Locale.getDefault()).contains(manufacturer))
                        return true
            return false
        }

    init {
        val devicesWithoutNavBar = HashSet<String>()
        devicesWithoutNavBar.add("HTC One V")
        devicesWithoutNavBar.add("HTC One S")
        devicesWithoutNavBar.add("HTC One X")
        devicesWithoutNavBar.add("HTC One XL")
        hasNavBar = !devicesWithoutNavBar.contains(Build.MODEL)
        val ctx = AppContextProvider.appContext
        val pm = ctx.packageManager
        hasTsp = pm == null || pm.hasSystemFeature("android.hardware.touchscreen")
        isAndroidTv = pm != null && pm.hasSystemFeature("android.software.leanback")
        watchDevices = isAndroidTv && Build.MODEL.startsWith("Bouygtel")
        isChromeBook = pm != null && pm.hasSystemFeature("org.chromium.arc.device_management")
        isTv = isAndroidTv || !isChromeBook && !hasTsp
        hasPlayServices = pm == null || hasPlayServices(pm)
        hasPiP = pm != null && pm.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
        pipAllowed = hasPiP || (hasTsp && !AndroidUtil.isOOrLater)
        val tm = ctx.getSystemService<TelephonyManager>()
        isPhone = tm == null || tm.phoneType != TelephonyManager.PHONE_TYPE_NONE
    }

    fun showInternalStorage() = Build.BRAND !in arrayOf("Swisscom", "BouyguesTelecom") && Build.BOARD != "sprint"

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
    fun getCenteredAxis(event: MotionEvent, device: InputDevice, axis: Int): Float {
        val range = device.getMotionRange(axis, event.source)

        // A joystick at rest does not always report an absolute position of
        // (0,0). Use the getFlat() method to determine the range of values
        // bounding the joystick axis center.
        if (range != null) {
            val flat = range.flat
            val value = event.getAxisValue(axis)

            // Ignore axis values that are within the 'flat' region of the
            // joystick axis center.
            if (value.absoluteValue > flat) {
                return value
            }
        }
        return 0f
    }

    fun canUseSystemNightMode(): Boolean {
        return Build.VERSION.SDK_INT > VERSION_CODES.P || Build.VERSION.SDK_INT == VERSION_CODES.P && "samsung" == Build.MANUFACTURER.lowercase(Locale.US)
    }

    private fun hasPlayServices(pm: PackageManager): Boolean {
        try {
            pm.getPackageInfoCompat("com.google.android.gsf", PackageManager.GET_SERVICES)
            return true
        } catch (ignored: PackageManager.NameNotFoundException) {
        }

        return false
    }

    fun isDex(ctx: Context): Boolean {
        if (!AndroidUtil.isNougatOrLater) return false
        return try {
            val config = ctx.resources.configuration
            val configClass = config.javaClass
            configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) == configClass.getField("semDesktopModeEnabled").getInt(config)
        } catch (ignored: Exception) {
            false
        }

    }

    fun hasToForceTV(): Boolean {
        return Build.MODEL in forcedTVModels
    }

    object MediaFolders {
        private val EXTERNAL_PUBLIC_MOVIES_DIRECTORY_FILE: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
        private val EXTERNAL_PUBLIC_MUSIC_DIRECTORY_FILE: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
        private val EXTERNAL_PUBLIC_PODCAST_DIRECTORY_FILE: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS)
        private val EXTERNAL_PUBLIC_DOWNLOAD_DIRECTORY_FILE: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        private val EXTERNAL_PUBLIC_DCIM_DIRECTORY_FILE: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
        private val WHATSAPP_VIDEOS_FILE: File = File("$EXTERNAL_PUBLIC_DIRECTORY/WhatsApp/Media/WhatsApp Video/")
        private val WHATSAPP_VIDEOS_FILE_A11: File = File("$EXTERNAL_PUBLIC_DIRECTORY/Android/media/com.whatsapp/WhatsApp/Media/WhatsApp Video/")
        private val EXTERNAL_PUBLIC_SCREENSHOTS_DIRECTORY: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES + "/Screenshots")

        val EXTERNAL_PUBLIC_MOVIES_DIRECTORY_URI = getFolderUri(EXTERNAL_PUBLIC_MOVIES_DIRECTORY_FILE)
        val EXTERNAL_PUBLIC_MUSIC_DIRECTORY_URI = getFolderUri(EXTERNAL_PUBLIC_MUSIC_DIRECTORY_FILE)
        val EXTERNAL_PUBLIC_PODCAST_DIRECTORY_URI = getFolderUri(EXTERNAL_PUBLIC_PODCAST_DIRECTORY_FILE)
        val EXTERNAL_PUBLIC_DOWNLOAD_DIRECTORY_URI = getFolderUri(EXTERNAL_PUBLIC_DOWNLOAD_DIRECTORY_FILE)
        val EXTERNAL_PUBLIC_DCIM_DIRECTORY_URI = getFolderUri(EXTERNAL_PUBLIC_DCIM_DIRECTORY_FILE)
        val WHATSAPP_VIDEOS_FILE_URI = getFolderUri(WHATSAPP_VIDEOS_FILE)
        val WHATSAPP_VIDEOS_FILE_URI_A11 = getFolderUri(WHATSAPP_VIDEOS_FILE_A11)
        val EXTERNAL_PUBLIC_SCREENSHOTS_URI_DIRECTORY = getFolderUri(EXTERNAL_PUBLIC_SCREENSHOTS_DIRECTORY)

        private fun getFolderUri(file: File): Uri {
            return try {
                "file://${file.canonicalPath}".toUri()
            } catch (ignored: IOException) {
                "file://${file.path}".toUri()
            }
        }

        fun isOneOfMediaFolders(uri: Uri) = EXTERNAL_PUBLIC_MOVIES_DIRECTORY_URI == uri || EXTERNAL_PUBLIC_MUSIC_DIRECTORY_URI == uri || EXTERNAL_PUBLIC_PODCAST_DIRECTORY_URI == uri || EXTERNAL_PUBLIC_DOWNLOAD_DIRECTORY_URI == uri || EXTERNAL_PUBLIC_DCIM_DIRECTORY_URI == uri || WHATSAPP_VIDEOS_FILE == uri
    }

    fun close(closeable: Closeable?): Boolean {
        if (closeable != null)
            try {
                closeable.close()
                return true
            } catch (e: IOException) {
            }

        return false
    }
}
